﻿using Newtonsoft.Json.Linq;
using Niknak.Social.Common.Http;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace Niknak.Social.Twitter
{
    /// <summary>
    /// Provides basic methods to search Twitter and return IMessage objects
    /// </summary>
    /// <typeparam name="Settings">Type that implements OAuthSettings to provide OAuth security details</typeparam>
    public static class Search<Settings> 
        where Settings : ISettings, new()
    {
        private const int DEFAULTCOUNT = 10;
        private const string DEFAULTRESULTTYPE = "mixed";
        private const string DEFAULTSINCEMESSAGEID = "1";
        private const string APITIMELINEURL = "https://api.twitter.com/1.1/search/tweets.json?q={0}&count={1}&result_type={2}&since_id={3}&include_entities=true";
        
        //#region Basic

        //public static List<Message> GetTweets(string query)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(query);
        //}

        //public static List<Message> GetTweets(string query, int count)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(query, count);
        //}

        //public static List<Message> GetTweets(string query, int count, string resultType)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(query, count, resultType);
        //}

        //public static List<Message> GetTweets(string query, int count, string resultType, string sinceMessageId)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(query, count, resultType, sinceMessageId);
        //}

        //#endregion

        //#region Bit of both

        //public static List<Tweet> GetTweets<Tweet>(string query)
        //    where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query);
        //}

        //public static List<Tweet> GetTweets<Tweet>(string query, int count)
        //   where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count);
        //}

        //public static List<Tweet> GetTweets<Tweet>(string query, int count, string resultType)
        //   where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count, resultType);
        //}

        //public static List<Tweet> GetTweets<Tweet>(string query, int count, string resultType, string sinceMessageId)
        //   where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count, resultType, sinceMessageId);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string query)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string query, int count)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string query, int count, string resultType)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count, resultType);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string query, int count, string resultType, string sinceMessageId)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count, resultType, sinceMessageId);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string query)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string query, int count)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string query, int count, string resultType)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count, resultType);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string query, int count, string resultType, string sinceMessageId)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(query, count, resultType, sinceMessageId);
        //}

        //#endregion

        #region Generic

        public static List<IMessage> GetTweets(string query)
        {
            if (string.IsNullOrEmpty(query))
                throw new ArgumentNullException("query");

            return GetTweets(query, DEFAULTCOUNT, DEFAULTRESULTTYPE, DEFAULTSINCEMESSAGEID);
        }

        public static List<IMessage> GetTweets(string query, int count)
        {
            if (string.IsNullOrEmpty(query))
                throw new ArgumentNullException("query");
            if (count <= 0)
                throw new ArgumentNullException("count");

            return GetTweets(query, count, DEFAULTRESULTTYPE, DEFAULTSINCEMESSAGEID);
        }

        public static List<IMessage> GetTweets(string query, int count, string resultType)
        {
            if (string.IsNullOrEmpty(query))
                throw new ArgumentNullException("query");
            if (count <= 0)
                throw new ArgumentNullException("count");

            return GetTweets(query, count, resultType, DEFAULTSINCEMESSAGEID);
        }

        public static List<IMessage> GetTweets(string query, int count, string resultType, string sinceMessageId)
        {
            if (string.IsNullOrEmpty(query))
                throw new ArgumentNullException("query");
            if (count <= 0)
                throw new ArgumentNullException("count");
            if (string.IsNullOrEmpty(sinceMessageId) || sinceMessageId == "0")
                throw new ArgumentNullException("sinceMessageId");

            List<IMessage> outList = new List<IMessage>();
            List<JObject> tweets = new List<JObject>();

            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            using (HttpClient client = new HttpClient(new OAuthMessageHandler<Settings>(new HttpClientHandler())))
            {
                if (client == null)
                    throw new Exception("Unable to initiate HttpClient object");

                string requestUrl = string.Format(
                    APITIMELINEURL,
                    System.Web.HttpUtility.UrlEncode(query),
                    count,
                    resultType.ToLower(),
                    sinceMessageId
                );

                Task retrieveTweets = client.GetAsync(requestUrl).ContinueWith(requestTask =>
                {
                    //get HTTP response from completed task
                    HttpResponseMessage response = requestTask.Result;

                    //check that response was successful or throw exception
                    response.EnsureSuccessStatusCode();

                    //necessary when retrieving more than about 15 tweets at a time
                    //MediaTypeFormatter.SkipStreamLimitChecks = true;

                    //read response asynchronously as JsonValue and dump into list for further processing
                    Task processTweets = response.Content.ReadAsAsync<JObject>().ContinueWith(readTask =>
                    {
                        JArray statuses = readTask.Result.Value<JArray>("statuses");
                        foreach (JObject status in statuses)
                            tweets.Add(status);
                    });

                    Task.WaitAll(processTweets);
                });

                Task.WaitAll(retrieveTweets);
            }

            if (tweets != null)
                foreach (JObject tweet in tweets)
                {
                    //we need to do some checks on the message retrieved to ensure it's not fubar
                    if (!Utils.IsValidTweetJson(tweet))
                        continue;

                    outList.Add(Utils.ParseTweetJson<Settings>(tweet));
                }

            return outList;
        }

        #endregion
    }
}
